package com.dana1.nos.old.generator;

import java.net.NetworkInterface;
import java.security.Provider;
import java.security.SecureRandom;
import java.security.Security;
import java.security.Provider.Service;
import java.text.DateFormat;
import java.text.SimpleDateFormat;
import java.util.Calendar;
import java.util.Date;
import java.util.Enumeration;
import java.util.TimeZone;
import java.util.UUID;

import com.dana1.nos.old.Generator;
import com.dana1.nos.old.ServiceObject;

public class TimeBasedUUIDGenerator implements Generator
{
	private static final SecureRandom random = new SecureRandom();

	// lsb
	private static final long nodeMacAddr =  getNodeMacAddr();
	private static transient long clockSeq = random.nextInt()& 0x1fffl;
	private static final long variant = 2l;

	// msbd
	public static final long EPOCH_OFFSET_MILLIS = 0xb1d069b5400l;
	private static final long version = 1l;
	private static long lastTimestamp=0;
	private static int nanosPart=0; 

	@Override
	public Object nextValue(Class<? extends ServiceObject> objectClass)
	{
		return timeBasedUUID();
	}
	
	private static UUID timeBasedUUID()
	{
		long lsb = 
			( nodeMacAddr   & 0x0000ffffffffffffl )
			| (clockSeq<<48 & 0x1fff000000000000l )
			| (variant<<62 &  0xc000000000000000l );
		long nextTimeStamp = nextTimestamp();
		long msb = 
			(nextTimeStamp>>48  &0x0000000000000fffl)
			| ( version<<12     &0x000000000000f000l)
			| (nextTimeStamp>>16&0x00000000ffff0000l)
			| (nextTimeStamp<<32&0xffffffff00000000l);
		return new UUID(msb,lsb);
	}
	
	// 48 bits (6 bytes)
	private static long getNodeMacAddr()  
	{
		long networkAddr = 0l;
		byte [] b = new byte[6];
		random.nextBytes(b);
		try
		{
			Enumeration<NetworkInterface> e = NetworkInterface.getNetworkInterfaces();
firstNetworkAddr:
			while (e.hasMoreElements())
			{
				NetworkInterface if1 = e.nextElement();
				if (if1.isUp()&&(!if1.isLoopback()))
				{
					b = if1.getHardwareAddress();
					break firstNetworkAddr;
				}
			}
		}
		catch (Exception e)
		{
		}
		for (int i = 0; (i < 6)&&(i < b.length); i++)
		{
			if (b[i]<0)
				networkAddr = networkAddr<<8 | ((b[i]&0x7fl)|0x80l);
			else
				networkAddr = networkAddr<<8 | (b[i]&0x7fl);
		}
		return networkAddr&0x0000ffffffffffffl;
	}
	

	private static long nextTimestamp()
	{
		long timestamp =((System.currentTimeMillis()+EPOCH_OFFSET_MILLIS)*10000) & 0x0FFFffffFFFFffffl;
		synchronized (TimeBasedUUIDGenerator.class) 
		{
			if ((timestamp!=lastTimestamp)||(++nanosPart>=10000))
			{
				while (timestamp==lastTimestamp)
				{
					try
					{
						TimeBasedUUIDGenerator.class.wait(1l);
					}
					catch (InterruptedException e)
					{
					}
					timestamp = ((System.currentTimeMillis()+EPOCH_OFFSET_MILLIS)*10000) & 0x0FFFffffFFFFffffl;
				}
				lastTimestamp = timestamp;
				nanosPart = 0;
			}
			timestamp+=nanosPart;
		}
		return timestamp;
	}
	
	
	public static void main(String [] args)
	{
		Calendar c1 = Calendar.getInstance(TimeZone.getTimeZone("UTC"));
		c1.setTimeInMillis(0l);
		c1.set(Calendar.YEAR, 1582);
		c1.set(Calendar.MONTH, Calendar.OCTOBER);
		c1.set(Calendar.DAY_OF_MONTH, 15);
		
		System.out.println(Long.toHexString(-c1.getTimeInMillis()));

		for (Provider p: Security.getProviders())
		{
			System.out.println(p.getName());
			System.out.println(p.getInfo());
			for (Service s: p.getServices())
			{
				System.out.print("\t"+s.getType());
				System.out.print("\t");
				System.out.println(s.getAlgorithm());
			}	
			System.out.println("================");
		}

		System.out.println(Long.toHexString(2l<<62|0x3fff000000000000l));
	
		UUID u = timeBasedUUID();
		System.out.println(u.node()+" = "+nodeMacAddr);
		System.out.println(u.variant() + " = 2 ");
		System.out.println(u.clockSequence() + " = " + clockSeq);
		System.out.println(u.version() + " = " + version);
		System.out.println(u.timestamp() + " = " + lastTimestamp);

		DateFormat sdf = SimpleDateFormat.getDateTimeInstance();
		sdf.setTimeZone(TimeZone.getTimeZone("UTC"));
		System.out.println(sdf.format(c1.getTime()));
		sdf.setTimeZone(TimeZone.getDefault());
		System.out.println(sdf.format(new Date((u.timestamp()/10000)-EPOCH_OFFSET_MILLIS)));
		
	}
	
	
}
